package org.bonn.ooka.core;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.bonn.ooka.model.IComponent;

public class Assembler {
	private String m_lastName = null;
	private URLClassLoader m_classLoader = null;
	private Map<Class<?>, List<Class<?>>> m_componentMapping = null;
	private Map<Class<?>, IComponent> m_instances = null;
	private XMLComponentMapping hierarchy = null;
	
	public Assembler(){
		this.m_componentMapping = new HashMap<>();
		this.m_instances = new HashMap<>();
	}
	
	public IComponent load(String xmlPath) throws Exception {
		List<Class<?>> dependencies = new ArrayList<>();
		Class<?> mainComp = null;
		IComponent retComp = null;
		
		this.hierarchy = XMLExtractor.readStructure(xmlPath);
		this.m_lastName = hierarchy.name;
		
		for(XMLComponentMapping subComp : hierarchy.dependencies){
			dependencies.add(this.loadClass(subComp.path, subComp.classpath));
		}
		mainComp = this.loadClass(hierarchy.path, hierarchy.classpath);
		retComp = (IComponent)mainComp.newInstance();
		retComp.setScope(CREUtil.stringToScope(hierarchy.scope));
		this.m_componentMapping.put(mainComp, dependencies);
		this.m_instances.put(mainComp, retComp);
		this.assemble(retComp, dependencies);
		
		return retComp;
	}


	public String getLastName() {
		if (this.m_lastName == null)
			throw new IllegalArgumentException("Muss vorher was laden, junge!");
		return this.m_lastName;
	}
	
	private Class<?> loadClass(String path, String name)
			throws ClassNotFoundException, InstantiationException,
			IllegalAccessException, IOException {
		File f = new File(path);

		System.out.println("Assembler.loadClass(), Path: " + path);
		System.out.println("Assembler.loadClass(), Name: " + name);
		
		if (this.m_classLoader == null)
			this.m_classLoader  = new URLClassLoader(new URL[] { f.toURI().toURL() }, Assembler.class.getClassLoader());
		else
			this.m_classLoader = new URLClassLoader(new URL[] { f.toURI().toURL() }, this.m_classLoader);
		Class<?> clazz = this.m_classLoader.loadClass(name);
		return clazz;
	}
	
	private void assemble(IComponent main, List<Class<?>> sub) {
		for (Method method : main.getClass().getMethods()) {
			if (method.isAnnotationPresent(Inject.class)) {	//alle @Inject-Methoden
				Class<?>[] methodParams = method.getParameterTypes();
				for (Class<?> paramInterface : methodParams) { //alle Parameter der Inject-Methode
					for(int i = 0; i < sub.size(); i++){
						for(Class<?> subInterface : sub.get(i).getInterfaces()){ 
							if(subInterface.equals(paramInterface)){ //Implementiert Sub-Komponente gesuchtes Interface
								try {
									IComponent subcomp = (IComponent) sub.get(i).newInstance();
									subcomp.setScope(CREUtil.stringToScope(this.hierarchy.dependencies.get(i).scope));
									if(main.getScope().equals(subcomp.getScope()))
										method.invoke(main, subcomp);
								} catch (IllegalAccessException
										| IllegalArgumentException
										| InvocationTargetException
										| InstantiationException e) {
									e.printStackTrace();
								}
							}
						}
					}
				}
			}
		}
	}

	public void reassemble(IComponent iComponent) {
		List<Class<?>> subComponents = this.m_componentMapping.get(iComponent.getClass());
		this.assemble(iComponent, subComponents);
	}
}
